/*
 * Copyright 2008 Sun Microsystems, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */
package com.sun.dtv.lwuit;

import com.sun.dtv.lwuit.events.*;
import com.sun.dtv.lwuit.Component;
import com.sun.dtv.lwuit.Display;
import java.util.Vector;

/**
 * Handles event dispatching while guaranteeing that all events would
 * be fired properly on the EDT regardless of their source. This class handles listener
 * registration/removal in a safe and uniform way. 
 * 
 * @author Shai Almog
 */
final class EventDispatcher implements Runnable {
    private Vector listeners;
    private Object[] pending;
    private Object pendingEvent;
    
    /**
     * Add a listener to the dispatcher that would receive the events when they occurs
     * 
     * @param listener a dispatcher listener to add
     */
    public synchronized void addListener(Object listener) {
        if(listeners == null) {
            listeners = new Vector();
        }
        listeners.addElement(listener);
    }
    
    /**
     * Remove the listener from the dispatcher
     *
     * @param listener a dispatcher listener to remove
     */
    public synchronized void removeListener(Object listener) {
        listeners.removeElement(listener);
    }
    
    /**
     * Fires the event safely on the EDT without risk of concurrency errors
     * 
     * @param ev the ActionEvent to fire to the listeners
     */
    public void fireActionEvent(ActionEvent ev) {
        if(listeners == null || listeners.size() == 0) {
            return;
        }
        ActionListener[] array;
        synchronized(this) {
            array = new ActionListener[listeners.size()];
            for(int iter = 0 ; iter < array.length ; iter++) {
                array[iter] = (ActionListener)listeners.elementAt(iter);
            }
        }
        // if we already are on the EDT just fire the event
        if(Display.getInstance().isEdt()) {
            fireActionSync(array, ev);
        } else {
            pending = array;
            pendingEvent = ev;
            Display.getInstance().callSeriallyAndWait(this);
        }
    }
    
    /**
     * Synchronious internal call for common code
     */
    private void fireActionSync(ActionListener[] array, ActionEvent ev) {
        for(int iter = 0 ; iter < array.length ; iter++) {
            array[iter].actionPerformed(ev);
        }
    }
    
    /**
     * Fires the event safely on the EDT without risk of concurrency errors
     * 
     * @param c the Component that gets the focus event
     */
    public void fireFocus(Component c) {
        if(listeners == null || listeners.size() == 0) {
            return;
        }
        FocusListener[] array;
        synchronized(this) {
            array = new FocusListener[listeners.size()];
            for(int iter = 0 ; iter < array.length ; iter++) {
                array[iter] = (FocusListener)listeners.elementAt(iter);
            }
        }
        // if we already are on the EDT just fire the event
        if(Display.getInstance().isEdt()) {
            fireFocusSync(array, c);
        } else {
            pending = array;
            pendingEvent = c;
            Display.getInstance().callSeriallyAndWait(this);
        }
    }
    
    /**
     * Synchronious internal call for common code
     */
    private void fireFocusSync(FocusListener[] array, Component c) {
        if(c.hasFocus()) {
            for(int iter = 0 ; iter < array.length ; iter++) {
                array[iter].focusGained(c);
            }
        } else {
            for(int iter = 0 ; iter < array.length ; iter++) {
                array[iter].focusLost(c);
            }
        }
    }

    /**
     * Do not invoke this method it handles the dispatching internally and serves
     * as an implementation detail
     */
    public final void run() {
        if(!Display.getInstance().isEdt()) {
            throw new IllegalStateException("This method should not be invoked by external code!");
        }
        
        if(pending instanceof ActionListener[]) {
            fireActionSync((ActionListener[])pending, (ActionEvent)pendingEvent);
            return;
        }

        if(pending instanceof FocusListener[]) {
            fireFocusSync((FocusListener[])pending, (Component)pendingEvent);
            return;
        }
    }
}
